﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Text;

namespace Player
{
    public class WindowsSessionGetHeader
    {
        public enum Variables : int
        {
            [Variable(Direction.Input)]
            SessionId,
            [Variable(Direction.Input)]
            AuditDatabaseId,
            [Variable(Direction.Output)]
            PixelFormat,
            [Variable(Direction.Output)]
            Palette,
            [Variable(Direction.Output)]
            User,
            [Variable(Direction.Output)]
            StartTime,
            [Variable(Direction.Output)]
            TimeStampOffSet,
            [Variable(Direction.Output)]
            Machine
        }

        public struct Input
        {
            [InputMapping((int)Variables.SessionId)]
            public Guid SessionId;

            [InputMapping((int)Variables.AuditDatabaseId)]
            public int AuditDatabaseId;

            public Input(Guid sessionId, int auditDatabaseId)
            {
                AuditDatabaseId = auditDatabaseId;
                SessionId = sessionId;
            }
        }

        public struct Output
        {
            [OutputMapping((int)Variables.PixelFormat, SqlDbType.TinyInt)]
            public short PixelFormat;
            [OutputMapping((int)Variables.Palette, SqlDbType.Binary, 2048)]
            public byte[] Palette;
            [OutputMapping((int)Variables.User, SqlDbType.NVarChar, 511)]
            public string User;
            [OutputMapping((int)Variables.StartTime, SqlDbType.DateTime)]
            public DateTime StartTime;
            [OutputMapping((int)Variables.TimeStampOffSet, SqlDbType.BigInt)]
            public long TimeStampOffset;
            [OutputMapping((int)Variables.Machine, SqlDbType.NVarChar, 255)]
            public string Machine;
        }

        public static Output ExecuteNonQuery(string connectionstring, Input input)
        {
            Output output = new Output();
            ExecuteNonQuery(input.SessionId, input.AuditDatabaseId, out output.PixelFormat, out output.Palette, out output.User, out output.StartTime, out output.TimeStampOffset, out output.Machine);
            return output;
        }

        public static Output ExecuteNonQuery(Guid sessionId, int auditDatabaseId, out short pixelFormat, out byte[] palette, out string user, out DateTime startTime, out long timestampOffset, out string machine)
        {

            pixelFormat = 0;
            palette = null;
            user = string.Empty;
            startTime = SqlDateTime.MinValue.Value;
            timestampOffset = 0;
            machine = string.Empty;

            string sql = SqlHelper.Build("SELECT", Variables.PixelFormat, "=", WashSession.PixelFormat, ",",
                                         Variables.Palette, "=", WashSession.ColorMap, ",",
                                         Variables.User, "=", SessionTable.UserName, ",",
                                         Variables.StartTime, "=", SessionTable.StartTime, ",",
                                         Variables.TimeStampOffSet, "=", SessionTable.TimeStampOffset, ",",
                                         Variables.Machine, "=", SessionTable.MachineName,
                               "FROM", typeof(WashSession), "WITH (NOLOCK)", "INNER JOIN", typeof(SessionTable), "WITH (NOLOCK)", "ON", WashSession.SessionId, "=", SessionTable.SessionId,
                               "WHERE", WashSession.SessionId, "=", Variables.SessionId
                               );
            try
            {
                string connectionString = App.ConnectionString;
                using (SqlConnection connection = new SqlConnection(connectionString))
                {
                    connection.Open();
                    using (SqlCommand command = new SqlCommand(sql, connection))
                    {
                        command.Parameters.AddWithValue("@SessionId", sessionId);

                        SqlParameter p = new SqlParameter("@PixelFormat", SqlDbType.SmallInt);
                        p.Direction = ParameterDirection.Output;
                        command.Parameters.Add(p);

                        p = new SqlParameter("@Palette", SqlDbType.VarBinary, 2048);
                        p.Direction = ParameterDirection.Output;
                        command.Parameters.Add(p);

                        p = new SqlParameter("@User", SqlDbType.NVarChar, 511);
                        p.Direction = ParameterDirection.Output;
                        command.Parameters.Add(p);

                        p = new SqlParameter("@StartTime", SqlDbType.DateTime);
                        p.Direction = ParameterDirection.Output;
                        command.Parameters.Add(p);

                        p = new SqlParameter("@TimeStampOffSet", SqlDbType.BigInt);
                        p.Direction = ParameterDirection.Output;
                        command.Parameters.Add(p);

                        p = new SqlParameter("@Machine", SqlDbType.NVarChar, 255);
                        p.Direction = ParameterDirection.Output;
                        command.Parameters.Add(p);

                        command.ExecuteNonQuery();

                        command.GetValue(Variables.PixelFormat, ref pixelFormat);
                        command.GetValue(Variables.Palette, ref palette);
                        command.GetValue(Variables.User, ref user);
                        command.GetValue(Variables.StartTime, ref startTime);
                        command.GetValue(Variables.TimeStampOffSet, ref timestampOffset);
                        command.GetValue(Variables.Machine, ref machine);
                    }
                }
            }
            catch (SqlException e)
            {
                throw e;
            }
            return new Output();
        }
    }

    public class WindowsSessionGetEvents
    {

        public enum Variables : int
        {
            [Variable(Direction.Input)]
            SessionId,

            [Variable(Direction.Input)]
            AuditDatabaseId,
        }

        public struct Input
        {
            [InputMapping((int)Variables.SessionId)]
            public Guid SessionId;

            [InputMapping((int)Variables.AuditDatabaseId)]
            public int AuditDatabaseId;

            public Input(Guid sessionId, int auditDatabaseId)
            {
                AuditDatabaseId = auditDatabaseId;
                SessionId = sessionId;
            }
        }

        public struct Output
        {
            [ColumnMapping(typeof(WashEvent), (int)WashEvent.SessionId)]
            public Guid SessionId;
            [ColumnMapping(typeof(WashEvent), (int)WashEvent.SequenceNumber)]
            public int SequenceNumber;
            [ColumnMapping(typeof(WashEvent), (int)WashEvent.SystemTime)]
            public long SystemTime;
            [ColumnMapping(typeof(WashEvent), (int)WashEvent.TimeStamp)]
            public long TimeStamp;
            [ColumnMapping(typeof(WashEvent), (int)WashEvent.Event)]
            public WashEventType Event;
            [ColumnMapping(typeof(WashEvent), (int)WashEvent.Pid)]
            public int Pid;
            [ColumnMapping(typeof(WashEvent), (int)WashEvent.WindowId)]
            public long WindowId;
            [ColumnMapping(typeof(WashEvent), (int)WashEvent.Module)]
            public string Module;
            [ColumnMapping(typeof(WashEvent), (int)WashEvent.Text)]
            public string Text;
            [ColumnMapping(typeof(WashEvent), (int)WashEvent.X)]
            public int X;
            [ColumnMapping(typeof(WashEvent), (int)WashEvent.Y)]
            public int Y;
            [ColumnMapping(typeof(WashEvent), (int)WashEvent.Width)]
            public int Width;
            [ColumnMapping(typeof(WashEvent), (int)WashEvent.Height)]
            public int Height;
        }
        public static IEnumerable<Output> Execute(string connectstring, Input input)
        {
            return null;
        }

    }

    public class WindowsSessionGetFirstFrameSet
    {
        public enum Variables : int
        {
            [Variable(Direction.Input)]
            SessionId,

            [Variable(Direction.Input)]
            AuditDatabaseId
        }

        public struct Input
        {
            [InputMapping((int)Variables.SessionId)]
            public Guid SessionId;

            [InputMapping((int)Variables.AuditDatabaseId)]
            public int AuditDatabaseId;

            public Input(Guid sessionId, int auditDatabaseId)
            {
                SessionId = sessionId;
                AuditDatabaseId = auditDatabaseId;
            }
        }

        public struct Output
        {
            [ColumnMapping(typeof(WashData), (int)WashData.SystemTime)]
            public long SystemTime;
            [ColumnMapping(typeof(WashData), (int)WashData.TimeStamp)]
            public long TimeStamp;
            [ColumnMapping(typeof(WashData), (int)WashData.Data)]
            public SqlBytes Data;
        }

        public static IEnumerable<Output> Execute(string connectionstring, Input input, CommandBehavior behavior)
        {
            string sql = SqlHelper.Build("SELECT TOP(1)", SqlHelper.List(WashData.SystemTime, WashData.TimeStamp, WashData.Data),
                              "FROM", typeof(WashData), "WITH (NOLOCK)", "WHERE", WashData.SessionId, "=", Variables.SessionId,
                              "ORDER BY", WashData.SequenceNumber, "ASC");

            SqlDataReader reader = null;

            string connectionString = connectionstring;
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                connection.Open();
                using (SqlCommand command = new SqlCommand(sql, connection))
                {
                    command.Parameters.AddWithValue("SessionId", App.SessionId);
                    reader = command.ExecuteReader();
                    try
                    {
                        while (reader.Read())
                        {
                            Output output = new Output();
                            output.SystemTime = long.Parse(reader.GetValue(0).ToString());
                            output.TimeStamp = long.Parse(reader.GetValue(1).ToString());
                            output.Data = reader.GetSqlBytes(2);
                            yield return output;
                        }
                    }
                    finally
                    {
                        if (reader != null)
                            reader.Dispose();
                    }
                }
            }
        }
    }

    public class WindowsSessionGetFrameSets
    {
        public enum Variables : int
        {
            [Variable(Direction.Input)]
            SessionId,
            [Variable(Direction.Input)]
            AuditDatabaseId,
            [Variable(Direction.Input)]
            StartFrom,
        }

        public struct Input
        {
            [InputMapping((int)Variables.SessionId)]
            public Guid SessionId;

            [InputMapping((int)Variables.AuditDatabaseId)]
            public int AuditDatabaseId;

            [InputMapping((int)Variables.StartFrom)]
            public long StartFrom;

            public Input(Guid sessionId, int auditDatabaseId, long startFrom)
            {
                SessionId = sessionId;
                AuditDatabaseId = auditDatabaseId;
                StartFrom = startFrom;
            }
        }

        public struct Output
        {
            [ColumnMapping(typeof(WashData), (int)WashData.SystemTime)]
            public long SystemTime;
            [ColumnMapping(typeof(WashData), (int)WashData.TimeStamp)]
            public long TimeStamp;
            [ColumnMapping(typeof(WashData), (int)WashData.Data)]
            public SqlBytes Data;
        }

        public static IEnumerable<Output> Execute(string connectionstring, Input input, CommandBehavior behavior)
        {
            string sql = SqlHelper.Build(
                    "DECLARE @time BIGINT;",
                    "SELECT TOP(1)", "@time=", WashData.TimeStamp, "FROM", typeof(WashData), "WITH (NOLOCK)",
                    "WHERE", WashData.SessionId, "=", Variables.SessionId,
                    "AND", WashData.TimeStamp, "<", Variables.StartFrom,
                    "ORDER BY", WashData.TimeStamp, "DESC;",

                    "SET @time = ISNULL(@time,", Variables.StartFrom, ");",

                    "SELECT", SqlHelper.List(WashData.SystemTime, WashData.TimeStamp, WashData.Data), "FROM", typeof(WashData), "WITH (NOLOCK)",
                    "WHERE", "(", WashData.SessionId, "=", Variables.SessionId, "AND", WashData.TimeStamp, ">=@time)",
                    "ORDER BY", WashData.TimeStamp, "ASC");

            string connectionString = connectionstring;
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                connection.Open();
                using (SqlCommand command = new SqlCommand(sql, connection))
                {
                    command.Parameters.AddWithValue("SessionId", input.SessionId);
                    command.Parameters.AddWithValue("StartFrom", input.StartFrom);

                    using (SqlDataReader reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            Output output = new Output();
                            output.SystemTime = long.Parse(reader.GetValue(0).ToString());
                            output.TimeStamp = long.Parse(reader.GetValue(1).ToString());
                            output.Data = reader.GetSqlBytes(2);
                            yield return output;
                        }
                    }
                }
            }
        }
    }

    public class WindowsSessionGetLastFrameSet
    {
        public enum Variables : int
        {
            [Variable(Direction.Input)]
            SessionId,

            [Variable(Direction.Input)]
            AuditDatabaseId
        }

        public struct Input
        {
            [InputMapping((int)Variables.SessionId)]
            public Guid SessionId;

            [InputMapping((int)Variables.AuditDatabaseId)]
            public int AuditDatabaseId;

            public Input(Guid sessionId, int auditDatabaseId)
            {
                SessionId = sessionId;
                AuditDatabaseId = auditDatabaseId;
            }
        }

        public struct Output
        {
            [ColumnMapping(typeof(WashData), (int)WashData.SystemTime)]
            public long SystemTime;
            [ColumnMapping(typeof(WashData), (int)WashData.TimeStamp)]
            public long TimeStamp;
            [ColumnMapping(typeof(WashData), (int)WashData.Data)]
            public SqlBytes Data;
        }

        public static IEnumerable<Output> Execute(string connectionstring, Input input, CommandBehavior behavior)
        {
            string sql = SqlHelper.Build("SELECT TOP(1)", SqlHelper.List(WashData.SystemTime, WashData.TimeStamp, WashData.Data),
                                "FROM", typeof(WashData), "WITH (NOLOCK)", "WHERE", WashData.SessionId, "=", Variables.SessionId,
                                "ORDER BY", WashData.SequenceNumber, "DESC");

            string connectionString = connectionstring;
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                connection.Open();
                using (SqlCommand command = new SqlCommand(sql, connection))
                {
                    command.Parameters.AddWithValue("SessionId", input.SessionId);

                    using (SqlDataReader reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            Output output = new Output();
                            output.SystemTime = long.Parse(reader.GetValue(0).ToString());
                            output.TimeStamp = long.Parse(reader.GetValue(1).ToString());
                            output.Data = reader.GetSqlBytes(2);
                            yield return output;
                        }
                    }
                }
            }

        }

    }

    public static class SqlHelper
    {
        //public static readonly Settings Settings = new Settings();
        //public const string CONTEXT_CONNECTION = "context connection=true";

        /// <summary>
        /// Build the SQL statement by appending different elements in the object array
        /// The item inside the object array can be table, view, columns, variables or 
        /// static string
        /// </summary>
        /// <param name="values"></param>
        /// <returns></returns>
        public static string Build(params object[] values)
        {
            StringBuilder sql = new StringBuilder();
            foreach (object value in values)
            {
                // Table or view
                if (value is Type)
                {
                    string name = null;

                    TableAttribute table = Attribute.GetCustomAttribute((Type)value, typeof(TableAttribute)) as TableAttribute;
                    if (table != null) name = table.Name;

                    ViewAttribute view = Attribute.GetCustomAttribute((Type)value, typeof(ViewAttribute)) as ViewAttribute;
                    if (view != null) name = view.Name;

                    if (name == null) throw new Exception("The type should have either TableAttribute or ViewAttribute defined");
                    sql.Append(" [" + name + "] ");
                    continue;
                }

                // Normal static string
                if (value is string)
                {
                    sql.Append(" " + value + " ");
                    continue;
                }

                if (value is Enum)
                {
                    Type type = value.GetType();

                    // Variables
                    VariableAttribute variable = Attribute.GetCustomAttribute(type.GetField(value.ToString()), typeof(VariableAttribute)) as VariableAttribute;
                    if (variable != null)
                    {
                        sql.Append(" @" + value + " ");
                        continue;
                    }

                    // Full column name
                    ColumnAttribute column = Attribute.GetCustomAttribute(type.GetField(value.ToString()), typeof(ColumnAttribute)) as ColumnAttribute;
                    if (column != null)
                    {
                        string name = null;

                        TableAttribute table = Attribute.GetCustomAttribute((Type)type, typeof(TableAttribute)) as TableAttribute;
                        if (table != null) name = table.Name;

                        ViewAttribute view = Attribute.GetCustomAttribute((Type)type, typeof(ViewAttribute)) as ViewAttribute;
                        if (view != null) name = view.Name;

                        if (name != null) sql.Append(" [" + name + "].[" + value + "] ");
                        else sql.Append(" [" + value + "] ");
                        continue;
                    }

                    // Property name
                    PropertyAttribute property = Attribute.GetCustomAttribute(type.GetField(value.ToString()), typeof(PropertyAttribute)) as PropertyAttribute;
                    if (property != null)
                    {
                        sql.Append(" '" + value + "' ");
                        continue;
                    }

                    // Convert enum with various underlying type to corresponding numeric value
                    sql.Append(System.Convert.ChangeType(value, ((Enum)value).GetTypeCode()));
                    continue;
                }

                if (value is Guid)
                {
                    sql.Append(" '" + value.ToString() + "' ");
                    continue;
                }

                if (value is byte[])
                {
                    sql.Append(" " + BytesToHexString((byte[])value) + " ");
                    continue;
                }

                if (value is int)
                {
                    sql.Append(" " + value.ToString() + " ");
                    continue;
                }

                if (value is long)
                {
                    sql.Append(" " + value.ToString() + " ");
                    continue;
                }

                throw new Exception("Failed to build SQL statement"); // TODO: pass out the objects
            }

            return sql.ToString();
        }

        /// <summary>
        /// Compose the sql statement for CONVERT function
        /// toType can be SqlDataType or SystemPropertyItem enum
        /// expression can be variable
        /// </summary>
        /// <param name="toType"></param>
        /// <param name="expression"></param>
        /// <returns></returns>
        public static string Convert(object toType, object expression)
        {
            StringBuilder sql = new StringBuilder();
            sql.Append("CONVERT(");

            if (toType is Enum)
            {
                Type type = toType.GetType();

                // Property name
                PropertyAttribute property = Attribute.GetCustomAttribute(type.GetField(toType.ToString()), typeof(PropertyAttribute)) as PropertyAttribute;
                if (property != null)
                {
                    sql.Append(property.Type);

                    if (property.MaxLength.HasValue)
                    {
                        sql.AppendFormat("({0})", (property.MaxLength == -1) ? "MAX" : property.MaxLength.ToString());
                    }
                }
                else if (toType is SqlDbType)
                {
                    sql.Append(toType);
                }
                else
                {
                    throw new Exception("Failed to build SQL statement. Invalid type in Convert.");
                }
            }
            else
            {
                throw new Exception("Failed to build SQL statement. Invalid type in Convert.");
            }

            sql.Append(",");
            sql.Append(Build(expression));
            sql.Append(")");
            return sql.ToString();
        }

        /// <summary>
        /// Concatencate comma between each element of the item list.  The item inside the
        /// item lists can be table, view, columns, variables or static string
        /// </summary>
        /// <param name="items"></param>
        /// <returns></returns>
        public static string List(params object[] items)
        {
            List<string> names = new List<string>();
            foreach (object item in items)
            {
                names.Add(Build(item));
            }

            return string.Join(", ", names.ToArray());
        }

        /// <summary>
        /// Convert byte array to hex string
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public static string BytesToHexString(byte[] data)
        {
            StringBuilder sb = new StringBuilder("0x");

            foreach (byte aByte in data)
            {
                sb.Append(aByte.ToString("X2"));
            }

            return sb.ToString();
        }


        public static void GetValue<TValue>(this SqlCommand command, Enum variable, ref TValue value)
        {
            GetValue<TValue>(command, variable, ref value, true);
        }

        public static void GetValue<TValue>(this SqlCommand command, Enum variable, ref TValue value, bool writeUnmaskTraceValue)
        {
            SqlParameter param = command.Parameters["@" + variable.ToString()];

            if (param.Value is TValue)
            {
                value = (TValue)param.Value;
            }
            else if (param.SqlValue is TValue)
            {
                value = (TValue)param.SqlValue;
            }

            //command.Connection.Trace("{0} = {1}", "@" + variable.ToString(), (writeUnmaskTraceValue ? value : (object)SqlTrace.MASKED_TRACE_VALUE));
        }

    }
}


